/*
 * Copyright 2015-2025 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.api;

import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.MAINTAINED;

import java.net.URI;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.junit.platform.commons.util.Preconditions;

/**
 * A {@code DynamicTest} is a test case generated at runtime.
 *
 * <p>It is composed of a {@linkplain DynamicNode#getDisplayName display name}
 * and an {@link #getExecutable Executable}.
 *
 * <p>Instances of {@code DynamicTest} must be generated by factory methods
 * annotated with {@link TestFactory @TestFactory}.
 *
 * <p>Note that dynamic tests are quite different from standard {@link Test @Test}
 * cases since callbacks such as {@link BeforeEach @BeforeEach} and
 * {@link AfterEach @AfterEach} methods are not executed for dynamic tests.
 *
 * @since 5.0
 * @see #dynamicTest(String, Executable)
 * @see #stream(Iterator, Function, ThrowingConsumer)
 * @see Test
 * @see TestFactory
 * @see DynamicContainer
 * @see Executable
 */
@API(status = MAINTAINED, since = "5.3")
public class DynamicTest extends DynamicNode {

	/**
	 * Factory for creating a new {@code DynamicTest} for the supplied display
	 * name and executable code block.
	 *
	 * @param displayName the display name for the dynamic test; never
	 * {@code null} or blank
	 * @param executable the executable code block for the dynamic test;
	 * never {@code null}
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	public static DynamicTest dynamicTest(String displayName, Executable executable) {
		return dynamicTest(config -> config.displayName(displayName).executable(executable));
	}

	/**
	 * Factory for creating a new {@code DynamicTest} for the supplied display
	 * name, custom test source {@link URI}, and executable code block.
	 *
	 * @param displayName the display name for the dynamic test; never
	 * {@code null} or blank
	 * @param testSourceUri a custom test source URI for the dynamic test; may
	 * be {@code null} if the framework should generate the test source based on
	 * the {@code @TestFactory} method
	 * @param executable the executable code block for the dynamic test;
	 * never {@code null}
	 * @since 5.3
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	public static DynamicTest dynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) {
		return dynamicTest(
			config -> config.displayName(displayName).testSourceUri(testSourceUri).executable(executable));
	}

	/**
	 * Factory for creating a new {@code DynamicTest} that is configured via the
	 * supplied {@link Consumer} of {@link Configuration}.
	 *
	 * @param configurer callback for configuring the resulting
	 * {@code DynamicTest}; never {@code null}.
	 *
	 * @since 6.1
	 */
	@API(status = EXPERIMENTAL, since = "6.1")
	public static DynamicTest dynamicTest(Consumer<? super Configuration> configurer) {
		var configuration = new DefaultConfiguration();
		configurer.accept(configuration);
		return new DynamicTest(configuration);
	}

	/**
	 * Generate a stream of dynamic tests based on the given generator and test
	 * executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Iterator}. See
	 * {@link #stream(Stream, Function, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputGenerator} is responsible for generating
	 * input values. A {@link DynamicTest} will be added to the resulting
	 * stream for each dynamically generated input value, using the given
	 * {@code displayNameGenerator} and {@code testExecutor}.
	 *
	 * @param inputGenerator an {@code Iterator} that serves as a dynamic
	 * <em>input generator</em>; never {@code null}
	 * @param displayNameGenerator a function that generates a display name
	 * based on an input value; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> generated by the {@code inputGenerator}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Stream, Function, ThrowingConsumer)
	 */
	public static <T> Stream<DynamicTest> stream(Iterator<T> inputGenerator,
			Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) {

		Preconditions.notNull(inputGenerator, "inputGenerator must not be null");

		return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false),
			displayNameGenerator, testExecutor);
	}

	/**
	 * Generate a stream of dynamic tests based on the given input stream and
	 * test executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Stream}. See
	 * {@link #stream(Iterator, Function, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputStream} is responsible for supplying input values.
	 * A {@link DynamicTest} will be added to the resulting stream for each
	 * dynamically supplied input value, using the given {@code displayNameGenerator}
	 * and {@code testExecutor}.
	 *
	 * @param inputStream a {@code Stream} that supplies dynamic input values;
	 * never {@code null}
	 * @param displayNameGenerator a function that generates a display name
	 * based on an input value; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.7
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Iterator, Function, ThrowingConsumer)
	 */
	@API(status = MAINTAINED, since = "5.7")
	public static <T> Stream<DynamicTest> stream(Stream<T> inputStream,
			Function<? super T, String> displayNameGenerator, ThrowingConsumer<? super T> testExecutor) {

		Preconditions.notNull(inputStream, "inputStream must not be null");
		Preconditions.notNull(displayNameGenerator, "displayNameGenerator must not be null");
		Preconditions.notNull(testExecutor, "testExecutor must not be null");

		return inputStream //
				.map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input)));
	}

	/**
	 * Generate a stream of dynamic tests based on the given generator and test
	 * executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Iterator}. See
	 * {@link #stream(Stream, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputGenerator} is responsible for generating
	 * input values and display names. A {@link DynamicTest} will be added to
	 * the resulting stream for each dynamically generated input value,
	 * using the given {@code testExecutor}.
	 *
	 * @param inputGenerator an {@code Iterator} with {@code Named} values
	 * that serves as a dynamic <em>input generator</em>; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> generated by the {@code inputGenerator}
	 * and used by the {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.8
	 *
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Stream, ThrowingConsumer)
	 * @see Named
	 */
	@API(status = MAINTAINED, since = "5.8")
	public static <T> Stream<DynamicTest> stream(Iterator<? extends Named<T>> inputGenerator,
			ThrowingConsumer<? super T> testExecutor) {
		Preconditions.notNull(inputGenerator, "inputGenerator must not be null");

		return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor);
	}

	/**
	 * Generate a stream of dynamic tests based on the given input stream and
	 * test executor.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Stream}. See
	 * {@link #stream(Iterator, ThrowingConsumer)} as an alternative.
	 *
	 * <p>The given {@code inputStream} is responsible for supplying input values
	 * and display names. A {@link DynamicTest} will be added to the resulting stream for
	 * each dynamically supplied input value, using the given {@code testExecutor}.
	 *
	 * @param inputStream a {@code Stream} that supplies dynamic {@code Named}
	 * input values; never {@code null}
	 * @param testExecutor a consumer that executes a test based on an input
	 * value; never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * and used by the {@code displayNameGenerator} and {@code testExecutor}
	 * @return a stream of dynamic tests based on the given generator and
	 * executor; never {@code null}
	 * @since 5.8
	 *
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Iterator, ThrowingConsumer)
	 * @see Named
	 */
	@API(status = MAINTAINED, since = "5.8")
	public static <T> Stream<DynamicTest> stream(Stream<? extends Named<T>> inputStream,
			ThrowingConsumer<? super T> testExecutor) {
		Preconditions.notNull(inputStream, "inputStream must not be null");
		Preconditions.notNull(testExecutor, "testExecutor must not be null");

		return inputStream //
				.map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload())));
	}

	/**
	 * Generate a stream of dynamic tests based on the given iterator.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Iterator}. See
	 * {@link #stream(Stream)} as an alternative.
	 *
	 * <p>The given {@code iterator} is responsible for supplying
	 * {@link Named} input values that provide an {@link Executable} code block.
	 * A {@link DynamicTest} comprised of both parts will be added to the
	 * resulting stream for each dynamically supplied input value.
	 *
	 * @param iterator an {@code Iterator} that supplies named executables;
	 * never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * @return a stream of dynamic tests based on the given iterator; never
	 * {@code null}
	 * @since 5.11
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Stream)
	 * @see NamedExecutable
	 */
	@API(status = MAINTAINED, since = "5.13.3")
	public static <T extends Named<E>, E extends Executable> Stream<DynamicTest> stream(
			Iterator<? extends T> iterator) {
		Preconditions.notNull(iterator, "iterator must not be null");

		return stream(StreamSupport.stream(spliteratorUnknownSize(iterator, ORDERED), false));
	}

	/**
	 * Generate a stream of dynamic tests based on the given input stream.
	 *
	 * <p>Use this method when the set of dynamic tests is nondeterministic in
	 * nature or when the input comes from an existing {@link Stream}. See
	 * {@link #stream(Iterator)} as an alternative.
	 *
	 * <p>The given {@code inputStream} is responsible for supplying
	 * {@link Named} input values that provide an {@link Executable} code block.
	 * A {@link DynamicTest} comprised of both parts will be added to the
	 * resulting stream for each dynamically supplied input value.
	 *
	 * @param inputStream a {@code Stream} that supplies named executables;
	 * never {@code null}
	 * @param <T> the type of <em>input</em> supplied by the {@code inputStream}
	 * @return a stream of dynamic tests based on the given stream; never
	 * {@code null}
	 * @since 5.11
	 * @see #dynamicTest(String, Executable)
	 * @see #stream(Iterator)
	 * @see NamedExecutable
	 */
	@API(status = MAINTAINED, since = "5.13.3")
	public static <T extends Named<E>, E extends Executable> Stream<DynamicTest> stream(
			Stream<? extends T> inputStream) {
		Preconditions.notNull(inputStream, "inputStream must not be null");

		return inputStream. //
				map(input -> dynamicTest(input.getName(), input.getPayload()));
	}

	private final Executable executable;

	private DynamicTest(DefaultConfiguration configuration) {
		super(configuration);
		this.executable = Preconditions.notNull(configuration.executable, "executable must not be null");
	}

	/**
	 * Get the {@code executable} code block associated with this {@code DynamicTest}.
	 */
	public Executable getExecutable() {
		return this.executable;
	}

	/**
	 * {@code Configuration} of a {@link DynamicTest}.
	 *
	 * @since 6.1
	 * @see DynamicTest#dynamicTest(Consumer)
	 */
	@API(status = EXPERIMENTAL, since = "6.1")
	public sealed interface Configuration extends DynamicNode.Configuration<Configuration> {

		/**
		 * Set the {@linkplain DynamicTest#getExecutable() executable} to use
		 * for the configured {@link DynamicTest}.
		 *
		 * @param executable the executable; never {@code null} or blank
		 * @return this configuration for method chaining
		 */
		Configuration executable(Executable executable);

	}

	static final class DefaultConfiguration extends AbstractConfiguration<Configuration> implements Configuration {

		private @Nullable Executable executable;

		@Override
		public Configuration executable(Executable executable) {
			this.executable = Preconditions.notNull(executable, "executable must not be null");
			return this;
		}

		@Override
		protected Configuration self() {
			return this;
		}
	}

}
